#include "CandidateWords.h"

// 构造函数
// 读入词典文件、索引文件并解析到内存
CandidateWords::CandidateWords(const string& cnDictFile, const string& cnIndexFile)
{
    // 打开中文词典文件
    std::ifstream ifsCnDict{cnDictFile};
    if (!ifsCnDict.is_open()) {
        std::cerr << cnDictFile << ": File open failed.\n";
        exit(1);
    }

    // 打开中文索引文件
    std::ifstream ifsCnIndex{cnIndexFile};
    if (!ifsCnIndex.is_open()) {
        std::cerr << cnIndexFile << ": File open failed.\n";
        exit(1);
    }

    // 解析词典内容到内存
    string line;
    string word;
    size_t wdCount;

    m_cnDict.push_back(pair<string, size_t>{}); // 0号下标不用，因为行号从1开始

    while (getline(ifsCnDict, line)) {
        std::istringstream iss{line};
        iss >> word;
        iss >> wdCount;
        m_cnDict.emplace_back(word, wdCount);
    }

    // 解析索引内容到内存
    while (getline(ifsCnIndex, line)) {
        std::istringstream iss{line};
        iss >> word;

        size_t idx;
        while (iss >> idx) {
            m_cnIndex[word].insert(idx);
        }
    }

    ifsCnDict.close();
    ifsCnIndex.close();
}


// 设置词典库文件、单字索引文件
void CandidateWords::setKeyWordsRecommender(const string &dictFile, const string &indexFile)
{
    // 打开词典文件
    std::ifstream ifsDict{dictFile};
    if (!ifsDict.is_open()) {
        std::cerr << dictFile << ": File open failed.\n";
        exit(1);
    }

    // 打开中文索引文件
    std::ifstream ifsIndex{indexFile};
    if (!ifsIndex.is_open()) {
        std::cerr << indexFile << ": File open failed.\n";
        exit(1);
    }

    // 解析词典内容到内存
    string line;
    string word;
    size_t wdCount;

    m_cnDict.push_back(pair<string, size_t>{}); // 0号下标不用，因为行号从1开始

    while (getline(ifsDict, line)) {
        std::istringstream iss{line};
        iss >> word;
        iss >> wdCount;
        m_cnDict.emplace_back(word, wdCount);
    }

    // 解析索引内容到内存
    while (getline(ifsIndex, line)) {
        std::istringstream iss{line};
        iss >> word;

        size_t idx;
        while (iss >> idx) {
            m_cnIndex[word].insert(idx);
        }
    }

    ifsDict.close();
    ifsIndex.close();
}


// 测试函数：打印中文词典
void CandidateWords::printCnDict(int lines)
{
    cout << "Total lines: " << m_cnDict.size() << endl;
    cout << "The first " << lines << "line(s) is as follow:\n";
    
    if (lines < 0) return;

    for (auto& [word, count]: m_cnDict) {
        cout << word << " " << count << endl;
        if (--lines <= 0) break;
    }
}
    

// 测试函数：打印中文索引
void CandidateWords::printCnIndex(int lines)
{
    cout << "Total lines: " << m_cnIndex.size() << endl;
    cout << "The first " << lines << "line(s) is as follow:\n";
    
    if (lines < 0) return;

    for (auto& [word, idxes] : m_cnIndex) {
        cout << word << " ";
        for (auto& idx : idxes) {
            cout << idx << " ";
        }
        cout << endl;
        if (--lines <= 0) break;
    }
}


// 实现std::greater<candidateType>的特化模板类
namespace std{
    template<>
    struct greater<candidateType>{  
        bool operator()(const candidateType& c1, const candidateType& c2) const{
            auto& [word1, minDist1, count1] = c1;
            auto& [word2, minDist2, count2] = c2;
            
            // 优先比较最短编辑距离
            if (minDist1 > minDist2) {
                return true;    // 让编辑距离较大的词向队列尾部移动
            } else if (minDist1 < minDist2) {
                return false;
            } else { // 最短编辑距离相同，比较候选词的词频
                if (count1 < count2) { 
                    return true;    // 让词频较小的词向队列尾部移动
                } else if (count1 > count2) {
                    return false;
                } else {  // 词频也相同，直接比较字符串
                    return word1 > word2; // 让较大的字符串向队列尾部移动
                }
            }
        }
    };
}

// 根据用户输入内容，分析出相关候选词并返回
Json CandidateWords::getCandidateWords(const string& input)
{   
    // 候选词初步集合（候选词，词频）
    // 使用unordered_map是为了去重，因为不同的字所对应的关键词可能有重复
    unordered_map<string, size_t> relatedWord_count;

    // 候选词优先队列
    // 升序排列所有候选词（比较规则见greater特化模板）
    std::priority_queue<candidateType, vector<candidateType>, std::greater<candidateType>> candidatesQ;

    // 将输入的关键词拆分成“单个汉字组成的序列”
    shared_ptr<vector<string>> keyWord = toSinoCharSequence(input);

    // 完成候选词初步集合
    for (int i = 0; i < keyWord->size(); ++i) {
        // 单字查询索引库，获得对应的词典行号集合
        auto it = m_cnIndex.find(keyWord->at(i));
        if (it == m_cnIndex.end()) { continue; }

        set<size_t>& dictLineNos = it->second;

        // 找到单个汉字在词典中对应的所有词, 并将其以及相关信息存入候选词初步集合
        for (auto& lineno : dictLineNos) {
            auto&[word, count] = m_cnDict[lineno];
            relatedWord_count[word] = count;
        }
    }

    // 完成候选词优先队列
    for (auto& [word, count] : relatedWord_count) {
        shared_ptr<vector<string>> candiWord = toSinoCharSequence(word);
        int minDist = minDistance(*keyWord, *candiWord);
        candidatesQ.emplace(word, minDist, count);  // 加入候选词优先队列
    }
    
    // 取出候选词队列的[至多]前5个词，以json字符串返回
    Json retVal = Json::array();
    int TopN = 5;  
    for (int i = 0; i < TopN && i < candidatesQ.size(); ++i) {
        auto& [word, _2, _3] = candidatesQ.top();
        retVal.push_back(word);
        candidatesQ.pop();
    }

    return retVal;
}


shared_ptr<vector<string>> CandidateWords::toSinoCharSequence(const string& sinoWord)
{
    shared_ptr<vector<string>> vecP{new vector<string>};

    const char* it = sinoWord.c_str();
    const char* end = sinoWord.c_str() + sinoWord.size();
    while (it != end) {
        auto start = it;
        utf8::next(it, end);    // 将it移动到下一个utf8字符所在的位置
        string sinoChar = string{ start, it };  // 一个汉字需要占用多个字节
        vecP->push_back(std::move(sinoChar));
    }

    return vecP;
}